<template>
  <div class="file-upload">
    <!-- 标签 -->
    <div v-if="label" class="upload-label">
      <span class="label-text">{{ label }}</span>
      <span v-if="required" class="required-mark">*</span>
      <el-tooltip v-if="tooltip" :content="tooltip" placement="top">
        <el-icon class="tooltip-icon"><QuestionFilled /></el-icon>
      </el-tooltip>
    </div>

    <!-- 上传组件 -->
    <div class="upload-wrapper" :class="{ 'has-error': hasError }">
      <el-upload
        v-model:file-list="fileList"
        v-bind="$attrs"
        :action="action"
        :headers="headers"
        :method="method"
        :multiple="multiple"
        :data="data"
        :name="name"
        :with-credentials="withCredentials"
        :show-file-list="showFileList"
        :drag="drag"
        :accept="accept"
        :list-type="listType"
        :auto-upload="autoUpload"
        :limit="limit"
        :on-exceed="handleExceed"
        :on-preview="handlePreview"
        :on-remove="handleRemove"
        :on-success="handleSuccess"
        :on-error="handleError"
        :on-progress="handleProgress"
        :on-change="handleChange"
        :before-upload="beforeUpload"
        :before-remove="beforeRemove"
        :http-request="customHttpRequest"
      >
        <!-- 拖拽上传区域 -->
        <template v-if="drag">
          <div class="upload-drag-area">
            <el-icon class="upload-icon"><Upload /></el-icon>
            <div class="upload-text">将文件拖到此处或<em>点击上传</em></div>
            <div class="upload-tip" v-if="tip">{{ tip }}</div>
          </div>
        </template>

        <!-- 普通上传按钮 -->
        <template v-else>
          <el-button type="primary">
            <el-icon><Upload /></el-icon>
            <span>{{ buttonText }}</span>
          </el-button>
          <div class="upload-tip" v-if="tip">{{ tip }}</div>
        </template>

        <!-- 文件列表插槽 -->
        <template v-if="$slots.file" #file="scope">
          <slot name="file" v-bind="scope"></slot>
        </template>
      </el-upload>

      <!-- 验证错误信息 -->
      <div v-if="hasError" class="error-message">
        {{ errorMessage }}
      </div>

      <!-- 帮助文本 -->
      <div v-if="helpText && !hasError" class="help-text">
        {{ helpText }}
      </div>
    </div>

    <!-- 预览弹窗 -->
    <el-dialog
      v-model="previewVisible"
      :title="previewTitle"
      width="800px"
      append-to-body
    >
      <div class="preview-content">
        <!-- 图片预览 -->
        <template v-if="isPreviewImage">
          <img :src="previewUrl" :alt="previewTitle" class="preview-image" />
        </template>

        <!-- 文件信息预览 -->
        <template v-else>
          <div class="file-info">
            <p><strong>文件名：</strong>{{ previewFile?.name }}</p>
            <p><strong>类型：</strong>{{ previewFile?.type }}</p>
            <p>
              <strong>大小：</strong>{{ formatFileSize(previewFile?.size) }}
            </p>
            <p>
              <strong>上传时间：</strong
              >{{ formatDate(previewFile?.uploadTime) }}
            </p>
          </div>
        </template>
      </div>
    </el-dialog>
  </div>
</template>

<script setup>
import { ref, computed } from "vue";
import { ElMessage } from "element-plus";
import { QuestionFilled, Upload } from "@element-plus/icons-vue";

const props = defineProps({
  modelValue: {
    type: Array,
    default: () => [],
  },
  label: {
    type: String,
    default: "",
  },
  action: {
    type: String,
    required: true,
  },
  headers: {
    type: Object,
    default: () => ({}),
  },
  method: {
    type: String,
    default: "post",
  },
  multiple: {
    type: Boolean,
    default: false,
  },
  data: {
    type: Object,
    default: () => ({}),
  },
  name: {
    type: String,
    default: "file",
  },
  withCredentials: {
    type: Boolean,
    default: false,
  },
  showFileList: {
    type: Boolean,
    default: true,
  },
  drag: {
    type: Boolean,
    default: false,
  },
  accept: {
    type: String,
    default: "",
  },
  listType: {
    type: String,
    default: "text",
    validator: (value) => ["text", "picture", "picture-card"].includes(value),
  },
  autoUpload: {
    type: Boolean,
    default: true,
  },
  limit: {
    type: Number,
    default: undefined,
  },
  buttonText: {
    type: String,
    default: "点击上传",
  },
  tip: {
    type: String,
    default: "",
  },
  required: {
    type: Boolean,
    default: false,
  },
  tooltip: {
    type: String,
    default: "",
  },
  helpText: {
    type: String,
    default: "",
  },
  rules: {
    type: Array,
    default: () => [],
  },
  maxSize: {
    type: Number,
    default: 0, // 单位：MB，0表示不限制
  },
});

const emit = defineEmits([
  "update:modelValue",
  "exceed",
  "preview",
  "remove",
  "success",
  "error",
  "progress",
  "change",
  "error-validate",
]);

const fileList = computed({
  get: () => props.modelValue,
  set: (value) => emit("update:modelValue", value),
});

const errorMessage = ref("");
const hasError = computed(() => !!errorMessage.value);

// 预览相关
const previewVisible = ref(false);
const previewFile = ref(null);
const previewUrl = ref("");
const previewTitle = computed(() => previewFile.value?.name || "文件预览");
const isPreviewImage = computed(() => {
  const type = previewFile.value?.type;
  return (
    type?.startsWith("image/") ||
    /\.(jpg|jpeg|png|gif|webp)$/i.test(previewFile.value?.name || "")
  );
});

// 验证文件
const validateFile = (file) => {
  // 验证文件大小
  if (props.maxSize > 0) {
    const isLtMaxSize = file.size / 1024 / 1024 < props.maxSize;
    if (!isLtMaxSize) {
      errorMessage.value = `文件大小不能超过 ${props.maxSize}MB`;
      return false;
    }
  }

  // 验证文件类型
  if (props.accept) {
    const acceptTypes = props.accept.split(",").map((type) => type.trim());
    const fileType = file.type;
    const fileName = file.name;
    const isValidType = acceptTypes.some((type) => {
      if (type.startsWith(".")) {
        return fileName.toLowerCase().endsWith(type.toLowerCase());
      }
      return fileType.match(new RegExp(type.replace("*", ".*")));
    });
    if (!isValidType) {
      errorMessage.value = "文件类型不符合要求";
      return false;
    }
  }

  errorMessage.value = "";
  return true;
};

// 自定义上传请求
const customHttpRequest = async ({ file, onProgress, onSuccess, onError }) => {
  try {
    const formData = new FormData();
    formData.append(props.name, file);

    // 添加额外数据
    Object.keys(props.data).forEach((key) => {
      formData.append(key, props.data[key]);
    });

    const xhr = new XMLHttpRequest();
    xhr.upload.addEventListener("progress", (e) => {
      if (e.lengthComputable) {
        const percent = Math.round((e.loaded * 100) / e.total);
        onProgress({ percent });
      }
    });

    xhr.addEventListener("load", () => {
      if (xhr.status >= 200 && xhr.status < 300) {
        const response = JSON.parse(xhr.responseText);
        onSuccess(response);
      } else {
        onError(new Error("上传失败"));
      }
    });

    xhr.addEventListener("error", () => {
      onError(new Error("上传失败"));
    });

    xhr.open(props.method, props.action, true);

    // 设置请求头
    if (props.withCredentials) {
      xhr.withCredentials = true;
    }
    Object.keys(props.headers).forEach((key) => {
      xhr.setRequestHeader(key, props.headers[key]);
    });

    xhr.send(formData);
  } catch (error) {
    onError(error);
  }
};

// 处理超出限制
const handleExceed = (files, uploadFiles) => {
  ElMessage.warning(`最多只能上传 ${props.limit} 个文件`);
  emit("exceed", files, uploadFiles);
};

// 处理预览
const handlePreview = (file) => {
  previewFile.value = file;
  previewUrl.value = file.url;
  previewVisible.value = true;
  emit("preview", file);
};

// 处理移除
const handleRemove = (file, uploadFiles) => {
  emit("remove", file, uploadFiles);
};

// 处理上传成功
const handleSuccess = (response, uploadFile, uploadFiles) => {
  emit("success", response, uploadFile, uploadFiles);
};

// 处理上传失败
const handleError = (error, uploadFile, uploadFiles) => {
  errorMessage.value = "文件上传失败";
  emit("error", error, uploadFile, uploadFiles);
};

// 处理上传进度
const handleProgress = (event, uploadFile, uploadFiles) => {
  emit("progress", event, uploadFile, uploadFiles);
};

// 处理文件状态改变
const handleChange = (uploadFile, uploadFiles) => {
  emit("change", uploadFile, uploadFiles);
};

// 上传前验证
const beforeUpload = (file) => {
  return validateFile(file);
};

// 移除前确认
const beforeRemove = (uploadFile, uploadFiles) => {
  return ElMessageBox.confirm(`确定移除 ${uploadFile.name}？`);
};

// 格式化文件大小
const formatFileSize = (size) => {
  if (!size) return "0 B";
  const units = ["B", "KB", "MB", "GB", "TB"];
  let index = 0;
  while (size >= 1024 && index < units.length - 1) {
    size /= 1024;
    index++;
  }
  return `${size.toFixed(2)} ${units[index]}`;
};

// 格式化日期
const formatDate = (date) => {
  if (!date) return "";
  return new Date(date).toLocaleString();
};
</script>

<style lang="scss" scoped>
.file-upload {
  @apply mb-4;

  .upload-label {
    @apply flex items-center mb-1;

    .label-text {
      @apply text-sm text-gray-700 font-medium;
    }

    .required-mark {
      @apply text-red-500 ml-1;
    }

    .tooltip-icon {
      @apply ml-1 text-gray-400 cursor-help;
    }
  }

  .upload-wrapper {
    @apply relative;

    &.has-error {
      :deep(.el-upload) {
        @apply border-red-500;
      }

      :deep(.el-upload-dragger) {
        @apply border-red-500;
      }
    }

    .upload-drag-area {
      @apply p-8 text-center;

      .upload-icon {
        @apply text-3xl text-gray-400 mb-2;
      }

      .upload-text {
        @apply text-gray-600;

        em {
          @apply text-primary-600 font-normal not-italic;
        }
      }
    }

    .upload-tip {
      @apply mt-2 text-sm text-gray-500;
    }

    .error-message {
      @apply mt-1 text-sm text-red-500;
    }

    .help-text {
      @apply mt-1 text-sm text-gray-500;
    }
  }
}

.preview-content {
  @apply flex items-center justify-center;

  .preview-image {
    @apply max-w-full max-h-[60vh] object-contain;
  }

  .file-info {
    @apply space-y-2 text-gray-600;

    strong {
      @apply text-gray-900;
    }
  }
}

:deep(.el-upload-list) {
  @apply mt-2;
}

:deep(.el-upload--picture-card),
:deep(.el-upload-list--picture-card .el-upload-list__item) {
  @apply w-32 h-32;
}
</style>
